#pragma once

#include "type.h"

#include "RHI.h"

#include "VertexDeclaration.h"

#include "ShaderCompiler.h"

namespace RenderSystem
{
	// Constants.
	enum { MAX_TEXCOORDS = 4 };

	class Shader;
	class VertexBuffer;
	class ShaderParameterMap;
	class SubMeshInstance;

	/**
	* A typed data source for a vertex factory which streams data from a vertex buffer.
	*/
	struct VertexStreamComponent
	{
		/** The vertex buffer to stream data from.  If null, no data can be read from this stream. */
		const VertexBuffer* pVertexBuffer;

		/** The offset of the data, relative to the beginning of each element in the vertex buffer. */
		BYTE Offset;

		/** The stride of the data. */
		BYTE Stride;

		/** The type of the data read from this stream. */
		BYTE Type;

		/** TRUE if the stream should be indexed by instance index instead of vertex index. */
		bool bUseInstanceIndex;

		/**
		* Initializes the data stream to null.
		*/
		VertexStreamComponent():
		pVertexBuffer(NULL),
			Offset(0),
			Stride(0),
			Type(VET_None),
			bUseInstanceIndex(FALSE)
		{}

		/**
		* Minimal initialization constructor.
		*/
		VertexStreamComponent(const VertexBuffer* InVertexBuffer,UINT InOffset,UINT InStride,UINT InType,bool bInUseInstanceIndex = FALSE):
		pVertexBuffer(InVertexBuffer),
			Offset(InOffset),
			Stride(InStride),
			Type(InType),
			bUseInstanceIndex(bInUseInstanceIndex)
		{}
	};

	/**
	* An interface to the parameter bindings for the vertex factory used by a shader.
	*/
	class VertexFactoryShaderParameters
	{
	public:
		virtual ~VertexFactoryShaderParameters() {}
		virtual void Bind( const ShaderParameterMap& ParameterMap ) = 0;
		virtual void Set( Shader* VertexShader, const class VertexFactory* VertexFactory ) const = 0;
		virtual void SetMesh( Shader* VertexShader, const SubMeshInstance& rSubMeshInstance ) const = 0;
	};

	/**
	* An object used to represent the type of a vertex factory.
	*/
	class VertexFactoryType
	{
	public:

		typedef VertexFactoryShaderParameters* (*ConstructParametersType)();
		typedef void (*ModifyCompilationEnvironmentType)( ShaderCompilerEnvironment& rEnv );

		typedef std::list< VertexFactoryType* >			VertexFactoryTypes;
		typedef VertexFactoryTypes::iterator			VertexFactoryTypesIterator;
		typedef VertexFactoryTypes::const_iterator		VertexFactoryTypesConstIterator;

		/**
		* @return The global shader factory list.
		*/
		static VertexFactoryTypes& GetTypeList();

		/**
		* Finds a VertexFactoryType by name.
		*/
		static VertexFactoryType* GetVFByName(const String& strVFName);

		/**
		* Minimal initialization constructor.
		* @param bInUsedWithMaterials - True if the vertex factory will be rendered in combination with a material.
		* @param bInSupportsStaticLighting - True if the vertex factory will be rendered with static lighting.
		*/
		VertexFactoryType(
			const String& strInName,
			const String& strInShaderFilename,
			bool bInUsedWithMaterials,
			bool bInSupportsStaticLighting,
			bool bInSupportsDynamicLighting,
			ConstructParametersType InConstructParameters,
			ModifyCompilationEnvironmentType InModifyCompilationEnvironment
			);

		// Accessors.
		const String& GetName() const { return m_strVFName; }
		const String& GetShaderFilename() const { return m_strShaderFilename; }
		VertexFactoryShaderParameters* CreateShaderParameters() const { return (*m_funcConstructParameters)(); }

		bool IsUsedWithMaterials() const { return m_bUsedWithMaterials; }

		bool SupportsStaticLighting() const { return m_bSupportsStaticLighting; }
		bool SupportsDynamicLighting() const { return m_bSupportsDynamicLighting; }

		/**
		* Calls the function ptr for the shader type on the given environment
		* @param Environment - shader compile environment to modify
		*/
		void ModifyCompilationEnvironment(ShaderCompilerEnvironment& rEnvironment)
		{
			(*m_funcModifyCompilationEnvironmentRef)( rEnvironment );
		}

	private:

		const String						m_strVFName;
		const String						m_strShaderFilename;
		bool								m_bUsedWithMaterials;
		bool								m_bSupportsStaticLighting;
		bool								m_bSupportsDynamicLighting;

		ConstructParametersType				m_funcConstructParameters;
		ModifyCompilationEnvironmentType	m_funcModifyCompilationEnvironmentRef;
	};

	/**
	* A macro which initializes a VertexStreamComponent to read a member from a struct.
	*/
#define STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer,VertexType,Member,MemberType) \
	VertexStreamComponent(VertexBuffer,STRUCT_OFFSET(VertexType,Member),sizeof(VertexType),MemberType)

	/**
	* A macro for declaring a new vertex factory type, for use in the vertex factory class's definition body.
	*/
#define DECLARE_VERTEX_FACTORY_TYPE(FactoryClass) \
	public: \
		static VertexFactoryType StaticType; \
		virtual VertexFactoryType* GetType() const { return &StaticType; }

	/**
	* A macro for implementing the static vertex factory type object, and specifying parameters used by the type.
	* @param bUsedWithMaterials - True if the vertex factory will be rendered in combination with a material.
	* @param bSupportsStaticLighting - True if the vertex factory will be rendered with static lighting.
	*/
#define IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderParametersType,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting) \
	VertexFactoryShaderParameters* Construct##FactoryClass##ShaderParameters() { return new ShaderParametersType(); } \
	VertexFactoryType FactoryClass::StaticType( \
	TEXT(#FactoryClass), \
	TEXT(ShaderFilename), \
	bUsedWithMaterials, \
	bSupportsStaticLighting, \
	bSupportsDynamicLighting, \
	Construct##FactoryClass##ShaderParameters, \
	FactoryClass::ModifyCompilationEnvironment, \
	);

	/**
	* Encapsulates a vertex data source which can be linked into a vertex shader.
	*/
	class VertexFactory
	{
	public:

		struct DataType
		{
			struct
			{
				/** The offset of the vertex light-map stream. */
				BYTE Offset;

				/** Stride of the vertex light-map stream if it stores directional samples. */
				BYTE DirectionalStride;

				/** Stride of the vertex light-map stream if it stores simple samples. */
				BYTE SimpleStride;

				/** TRUE if the vertex light-map stream should be indexed by instance index instead of vertex index. */
				bool bUseInstanceIndex;

			} LightMapStream;

			/** The number of instances. */
			UINT NumInstances;

			/** Initialization constructor. */
			DataType();
		};

		/**
		* @return The vertex factory's type.
		*/
		virtual VertexFactoryType* GetType() const = 0;

		/**
		* Activates the vertex factory.
		*/
		void Set() const;

		/**
		* Sets the position stream as the current stream source.
		*/
		void SetPositionStream() const;

		/**
		* Activates the vertex factory for rendering a vertex shadow-map.
		*/
		void SetVertexShadowMap(const VertexBuffer* pShadowMapVertexBuffer) const;

		/**
		* Activates the vertex factory for rendering a vertex light-map.
		*/
		void SetVertexLightMap(const VertexBuffer* pLightMapVertexBuffer,bool bUseDirectionalLightMap) const;

		/**
		* Activates the vertex factory for rendering a vertex light-map and vertex shadow-map at the same time.
		*/
		void SetVertexLightMapAndShadowMap(const VertexBuffer* pLightMapVertexBuffer, const VertexBuffer* pShadowMapVertexBuffer) const;

		/**
		* Can be overridden by VertexFactory subclasses to modify their compile environment just before compilation occurs.
		*/
		static void ModifyCompilationEnvironment(ShaderCompilerEnvironment& rOutEnvironment) {}

		// Accessors.
		VertexDeclaration& GetDeclaration() { return m_Declaration; }
		void SetDeclaration( const VertexDeclaration& rNewDeclaration ) { m_Declaration = rNewDeclaration; }

		const VertexDeclaration& GetDeclaration() const { return m_Declaration; }
		const VertexDeclaration& GetPositionDeclaration() const { return m_PositionDeclaration; }
		const VertexDeclaration& GetVertexShadowMapDeclaration() const { return m_VertexShadowMapDeclaration; }
		const VertexDeclaration& GetVertexLightMapDeclaration( bool bUseDirectionalLightMap ) const 
		{ 
			return bUseDirectionalLightMap ? m_DirectionalVertexLightMapDeclaration : m_SimpleVertexLightMapDeclaration; 
		}
		const VertexDeclaration& GetVertexLightMapAndShadowMapDeclaration() const 
		{ 
			return m_DirectionalVertexLightMapAndShadowMapDeclaration; 
		}

		virtual bool IsGPUSkinned() const { return false; }

		/**
		* Fill in array of strides from this factory's vertex streams without shadow/light maps
		* @param OutStreamStrides - output array of # MaxVertexElementCount stream strides to fill
		*/
		UINT GetStreamStrides( DWORD* pOutStreamStrides, bool bPadWithZeroes = true ) const;
		/**
		* Fill in array of strides from this factory's position only vertex streams
		* @param OutStreamStrides - output array of # MaxVertexElementCount stream strides to fill
		*/
		void GetPositionStreamStride(DWORD* pOutStreamStrides) const;
		/**
		* Fill in array of strides from this factory's vertex streams when using shadow-maps
		* @param OutStreamStrides - output array of # MaxVertexElementCount stream strides to fill
		*/
		void GetVertexShadowMapStreamStrides(DWORD* pOutStreamStrides) const;
		/**
		* Fill in array of strides from this factory's vertex streams when using light-maps
		* @param OutStreamStrides - output array of # MaxVertexElementCount stream strides to fill
		*/
		void GetVertexLightMapStreamStrides(DWORD* pOutStreamStrides,bool bUseDirectionalLightMap) const;
		/**
		* Fill in array of strides from this factory's vertex streams when using light-maps and shadow-maps at the same time
		* @param OutStreamStrides - output array of # MaxVertexElementCount stream strides to fill
		*/
		void GetVertexLightMapAndShadowMapStreamStrides(DWORD* pOutStreamStrides) const;

	protected:

		/**
		* Creates a vertex element for a vertex stream components.  Adds a unique stream index for the vertex buffer used by the component.
		* @param Component - The vertex stream component.
		* @param Usage - The vertex element usage semantics.
		* @param UsageIndex - the vertex element usage index.
		* @return The vertex element which corresponds to Component.
		*/
		VertexElement AccessStreamComponent(const VertexStreamComponent& rComponent,BYTE Usage,BYTE UsageIndex = 0);

		/**
		* Creates a vertex element for a position vertex stream component.  Adds a unique position stream index for the vertex buffer used by the component.
		* @param Component - The position vertex stream component.
		* @param Usage - The vertex element usage semantics.
		* @param UsageIndex - the vertex element usage index.
		* @return The vertex element which corresponds to Component.
		*/
		VertexElement AccessPositionStreamComponent(const VertexStreamComponent& Component,BYTE Usage,BYTE UsageIndex = 0);

		/**
		* Initializes the vertex declaration.
		* @param Elements - The elements of the vertex declaration.
		*/
		void InitDeclaration(
			const VertexDeclarationElementList& rElements, 
			const DataType& rData, 
			bool bCreateShadowMapDeclaration = true, 
			bool bCreateDirectionalLightMapDeclaration = true, 
			bool bCreateSimpleLightMapDeclaration = true);

		/**
		* Initializes the position-only vertex declaration.
		* @param Elements - The elements of the vertex declaration.
		*/
		void InitPositionDeclaration(const VertexDeclarationElementList& Elements);

	private:

		struct VertexStream
		{
			const VertexBuffer* pVertexBuffer;
			uint16				uStride;
			bool				bUseInstanceIndex;

			friend bool operator==(const VertexStream& A,const VertexStream& B)
			{
				return A.pVertexBuffer == B.pVertexBuffer && A.uStride == B.uStride && A.bUseInstanceIndex == B.bUseInstanceIndex;
			}
		};

		/** The vertex streams used to render the factory. */
		std::vector<VertexStream*>	m_Streams;

		/** The position only vertex stream used to render the factory during depth only passes. */
		std::vector<VertexStream*>	m_PositionStream;

		/** The RHI vertex declaration used to render the factory normally. */
		VertexDeclaration			m_Declaration;

		/** The RHI vertex declaration used to render the factory during depth only passes. */
		VertexDeclaration			m_PositionDeclaration;

		/** The RHI vertex declaration used to render the factory with a vertex shadow-map. */
		VertexDeclaration			m_VertexShadowMapDeclaration;

		/** The RHI vertex declaration used to render the factory with a directional vertex light-map. */
		VertexDeclaration			m_DirectionalVertexLightMapDeclaration;

		/** The RHI vertex declaration used to render the factory with a simple vertex light-map. */
		VertexDeclaration			m_SimpleVertexLightMapDeclaration;

		/** The RHI vertex declaration used to render the factory with a directional vertex light-map and a vertex shadow-map at the same time. */
		VertexDeclaration			m_DirectionalVertexLightMapAndShadowMapDeclaration;

		/** The vertex factory's data. */
		DataType					m_Data;
	};
}